今天我們來看看 Error handling 的部分吧!也就是如何處理異常和錯誤。異常和錯誤又有點不一樣,錯誤往往是指非預期的情況,必須透過修改程式來解決,而異常比較是不規則的,是可以預期的,例如讀取檔案,本來就是有可能會因為沒有檔案而無法讀取。有一些語言是以拋出預先定義或是自定義的 Exception,將邏輯以及處理錯誤的程式分開,處理錯誤的程式捕獲 Exception 後進行處理,像是 Python 和 Scala。而另一種常見的則是返回錯誤,像是 Golang 以及 Rust,由程式的呼叫者自行了解到錯誤並進行處理。現在就讓我們來看看吧!
try:
print(int(input().strip()))
except ValueError:
print("Bad String")
ValueError
。而在 Python 我們用的是 try...except
,來去執行我們預期會拋出 Exception 的程式,並且在 except
區塊去比對 Exception 的型態。這裡因為我們已經知道會拋出的型態是 ValueError
,所以可以這樣寫。如果沒有加上 ValueError
就表示只要有任何型態的 Exception 都會被捕獲,並且執行區塊內的程式碼。假使我們知道有多種 Exception 需要做不同的處理,就可以寫多個 except <Exception Type>
來去處理不同狀況,並且在最後有一個單獨的 except
來處理當沒有比對到任何已知的 Exception 型態時要做什麼事。假如沒有這麼做,最後沒有捕捉到的話,就會出現 Traceback 並且中斷程式囉!try...except
還可以加上 finally
,也就是當不管有沒有拋出異常,都一定會執行的部分,大多數情況會用來關閉資源。例如下面的程式不管怎樣都會在 finally
去做關閉。這裡順道提一下就是因為關閉資源已經變成很多資源開啟後的例行工作,所以 Python 提供了 with
這個語法讓我們不用再自己去寫像是 file.close()
之類的程式,可以參考這裡
file = open('test.py', 'r', encoding='UTF-8')
try:
for line in file:
print(line, end='')
except:
print('Failed to read file')
finally:
file.close()
raise <Exception Type>
來完成。def parseInteger(s: String): Option[Int] = {
try {
Some(Integer.parseInt(s.trim))
} catch {
case e: Exception => None
}
}
parseInteger
是幫我們把字串轉成整數,而回傳的 Type 是 Option[Int]
。我們之前說過 Option
有兩個 Subclass 分別是 Some
跟 None
,如果有值就是 Some(<value>)
反之則是 None
。而在這個 Function 裏頭是一個 try...catch
的語法,相當於 Python 的 try...except
,要是 try
區塊的程式拋出了 Exception,則會執行catch
的內容,而如同 Python,在 catch
裡頭也可以去比對是什麼樣的 Exception。這邊 case e: Exception => None
指的就是假設 Exception 是 Exception
就回傳 None
。我們也可以寫成 case e: NumberFormatException => None
也就是把真正的 Exception 類型寫出來。(Exception
是所有 Exception 的父類別) 因此可以比對不同類型的 Exception 做相對應的處理。那麼為什麼我們要特地將 try...catch
包一層並回傳 Option
呢?因為這樣的寫法才符合 Functional way,也就是執行 Function 的人一定會拿到回傳值,而不是得到 null
或是一個 Exception。i, err := strconv.Atoi(s)
if err != nil {
// Error handling
}
strconv
這個 Package 文件中的 Atoi
方法,是用來把字串轉成整數,而其回傳兩個值,第一個是轉換出來的整數,第二個則是一個 error
,error
是個 interface (之後會細講 interface),目前可以先當作一個類型。假使轉換整數成功,第二個參數就會回 nil
,表示沒有 Error。所以如同上面程式碼,常見的方式會是判斷 err != nil
來做錯誤處理。fn toInt(number_str: &str) -> i32 {
match number_str.parse::<i32>() {
Ok(n) => n,
Err(err) => 0,
}
}
panic!
這個 Macro,這個我們之後會再談。今天要談的是可恢復的,也就是可以經由得知錯誤後進行處理,而在 Rust 並沒有 Exception,而是回傳 Result
這個枚舉類型。沒錯!我們之前談過了 Result
分別有 Ok
跟 Err
兩種 variant。如果要呼叫一個可能出錯的函式,那麼其就應該回傳 Result
。這樣的好處是,因為回傳的是 Result
,所以一定要同時處理不管是 Ok
或是 Err
,也就是說錯誤處理成了邏輯的一部分。以上面的程式為例,我們會透過 match
來去比對是 Ok
還是 Err
則作出對應的處理,不過就如同我們先前提過的,因為這樣寫比較瑣碎,所以可以寫成 number_str.parse::<i32>().unwrap_or(0)
,就相當於上述 match
的區塊囉!假使今天 Err
所帶的值 err
會根據不同錯誤狀況而不同時,也是可以在增加比對的分支,來對不同的錯誤進行處理囉!